/**
 * Copyright (C) 2011 Ron Coleman. Contact: ronncoleman@gmail.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package bj.actor

import scala.actors.Actor
import scala.actors.OutputChannel
import bj.card.Hand
import bj.hkeeping.Ok
import bj.card.Card
import bj.util.Logs
import bj.hkeeping.Broke
import scala.collection.mutable.HashMap
import bj.actor.Player._

/**
 * @author Vikram
 *
 */
object BotPlayer {

  // Hash Map for Hard Basic Strategy 
  val hardBasicStrategy = HashMap[(String, String), String](

    ("9", "2") -> "Hit", ("9", "3") -> "DoubleDown", ("9", "4") -> "DoubleDown", ("9", "5") -> "DoubleDown", ("9", "6") -> "DoubleDown", ("9", "7") -> "Hit", ("9", "8") -> "Hit", ("9", "9") -> "Hit", ("9", "10") -> "Hit", ("9", "A") -> "Hit",
    ("10", "2") -> "DoubleDown", ("10", "3") -> "DoubleDown", ("10", "4") -> "DoubleDown", ("10", "5") -> "DoubleDown", ("10", "6") -> "DoubleDown", ("10", "7") -> "DoubleDown", ("10", "8") -> "DoubleDown", ("10", "9") -> "DoubleDown", ("10", "10") -> "Hit", ("10", "A") -> "Hit",
    ("11", "2") -> "DoubleDown", ("11", "3") -> "DoubleDown", ("11", "4") -> "DoubleDown", ("11", "5") -> "DoubleDown", ("11", "6") -> "DoubleDown", ("11", "7") -> "DoubleDown", ("11", "8") -> "DoubleDown", ("11", "9") -> "DoubleDown", ("11", "10") -> "DoubleDown", ("11", "A") -> "Hit",
    ("12", "2") -> "Hit", ("12", "3") -> "Hit", ("12", "4") -> "Stand", ("12", "5") -> "Stand", ("12", "6") -> "Stand", ("12", "7") -> "Hit", ("12", "8") -> "Hit", ("12", "9") -> "Hit", ("12", "10") -> "Hit", ("12", "A") -> "Hit",
    ("13", "2") -> "Stand", ("13", "3") -> "Stand", ("13", "4") -> "Stand", ("13", "5") -> "Stand", ("13", "6") -> "Stand", ("13", "7") -> "Hit", ("13", "8") -> "Hit", ("13", "9") -> "Hit", ("13", "10") -> "Hit", ("13", "A") -> "Hit",
    ("14", "2") -> "Stand", ("14", "3") -> "Stand", ("14", "4") -> "Stand", ("14", "5") -> "Stand", ("14", "6") -> "Stand", ("14", "7") -> "Hit", ("14", "8") -> "Hit", ("14", "9") -> "Hit", ("14", "10") -> "Hit", ("14", "A") -> "Hit",
    ("15", "2") -> "Stand", ("15", "3") -> "Stand", ("15", "4") -> "Stand", ("15", "5") -> "Stand", ("15", "6") -> "Stand", ("15", "7") -> "Hit", ("15", "8") -> "Hit", ("15", "9") -> "Hit", ("15", "10") -> "Hit", ("15", "A") -> "Hit",
    ("16", "2") -> "Stand", ("16", "3") -> "Stand", ("16", "4") -> "Stand", ("16", "5") -> "Stand", ("16", "6") -> "Stand", ("16", "7") -> "Hit", ("16", "8") -> "Hit", ("16", "9") -> "Hit", ("16", "10") -> "Hit", ("16", "A") -> "Hit",
    ("17", "2") -> "Stand", ("17", "3") -> "Stand", ("17", "4") -> "Stand", ("17", "5") -> "Stand", ("17", "6") -> "Stand", ("17", "7") -> "Stand", ("17", "8") -> "Stand", ("17", "9") -> "Stand", ("17", "10") -> "Stand", ("17", "A") -> "Stand")

  // Hash map for Total Soft Basic Strategy
  val softBasicStrategy = HashMap[(String, String), String](

    ("A,2", "2") -> "Hit", ("A,2", "3") -> "Hit", ("A,2", "4") -> "Hit", ("A,2", "5") -> "DoubleDown", ("A,2", "6") -> "DoubleDown", ("A,2", "7") -> "Hit", ("A,2", "8") -> "Hit", ("A,2", "9") -> "Hit", ("A,2", "10") -> "Hit", ("A,2", "A") -> "Hit",
    ("A,3", "2") -> "Hit", ("A,3", "3") -> "Hit", ("A,3", "4") -> "Hit", ("A,3", "5") -> "DoubleDown", ("A,3", "6") -> "DoubleDown", ("A,3", "7") -> "Hit", ("A,3", "8") -> "Hit", ("A,3", "9") -> "Hit", ("A,3", "10") -> "Hit", ("A,3", "A") -> "Hit",
    ("A,4", "2") -> "Hit", ("A,4", "3") -> "Hit", ("A,4", "4") -> "DoubleDown", ("A,4", "5") -> "DoubleDown", ("A,4", "6") -> "DoubleDown", ("A,4", "7") -> "Hit", ("A,4", "8") -> "Hit", ("A,4", "9") -> "Hit", ("A,4", "10") -> "Hit", ("A,4", "A") -> "Hit",
    ("A,5", "2") -> "Hit", ("A,5", "3") -> "Hit", ("A,5", "4") -> "DoubleDown", ("A,5", "5") -> "DoubleDown", ("A,5", "6") -> "DoubleDown", ("A,5", "7") -> "Hit", ("A,5", "8") -> "Hit", ("A,5", "9") -> "Hit", ("A,5", "10") -> "Hit", ("A,5", "A") -> "Hit",
    ("A,6", "2") -> "Hit", ("A,6", "3") -> "DoubleDown", ("A,6", "4") -> "DoubleDown", ("A,6", "5") -> "DoubleDown", ("A,6", "6") -> "DoubleDown", ("A,6", "7") -> "Hit", ("A,6", "8") -> "Hit", ("A,6", "9") -> "Hit", ("A,6", "10") -> "Hit", ("A,6", "A") -> "Hit",
    ("A,7", "2") -> "Stand", ("A,7", "3") -> "DoubleDown", ("A,7", "4") -> "DoubleDown", ("A,7", "5") -> "DoubleDown", ("A,7", "6") -> "DoubleDown", ("A,7", "7") -> "Stand", ("A,7", "8") -> "Stand", ("A,7", "9") -> "Hit", ("A,7", "10") -> "Hit", ("A,7", "A") -> "Hit",
    ("A,8", "2") -> "Stand", ("A,8", "3") -> "Stand", ("A,8", "4") -> "Stand", ("A,8", "5") -> "Stand", ("A,8", "6") -> "Stand", ("A,8", "7") -> "Stand", ("A,8", "8") -> "Stand", ("A,8", "9") -> "Stand", ("A,8", "10") -> "Stand", ("A,8", "A") -> "Stand",
    ("A,9", "2") -> "Stand", ("A,9", "3") -> "Stand", ("A,9", "4") -> "Stand", ("A,9", "5") -> "Stand", ("A,9", "6") -> "Stand", ("A,9", "7") -> "Stand", ("A,9", "8") -> "Stand", ("A,9", "9") -> "Stand", ("A,9", "10") -> "Stand", ("A,9", "A") -> "Stand",
    ("A,10", "2") -> "Stand", ("A,10", "3") -> "Stand", ("A,10", "4") -> "Stand", ("A,10", "5") -> "Stand", ("A,10", "6") -> "Stand", ("A,10", "7") -> "Stand", ("A,10", "8") -> "Stand", ("A,10", "9") -> "Stand", ("A,10", "10") -> "Stand", ("A,10", "A") -> "Stand")

  // Hash map for Total Soft Basic Strategy
  val pairBasicStrategy = HashMap[(String, String), String](

    ("2,2", "2") -> "Hit", ("2,2", "3") -> "Hit", ("2,2", "4") -> "Split", ("2,2", "5") -> "Split", ("2,2", "6") -> "Split", ("2,2", "7") -> "Split", ("2,2", "8") -> "Hit", ("2,2", "9") -> "Hit", ("2,2", "10") -> "Hit", ("2,2", "A") -> "Hit",
    ("3,3", "2") -> "Hit", ("3,3", "3") -> "Hit", ("3,3", "4") -> "Split", ("3,3", "5") -> "Split", ("3,3", "6") -> "Split", ("3,3", "7") -> "Split", ("3,3", "8") -> "Hit", ("3,3", "9") -> "Hit", ("3,3", "10") -> "Hit", ("3,3", "A") -> "Hit",
    ("4,4", "2") -> "Hit", ("4,4", "3") -> "Hit", ("4,4", "4") -> "Hit", ("4,4", "5") -> "Hit", ("4,4", "6") -> "Hit", ("4,4", "7") -> "Hit", ("4,4", "8") -> "Hit", ("4,4", "9") -> "Hit", ("4,4", "10") -> "Hit", ("4,4", "A") -> "Hit",
    ("5,5", "2") -> "DoubleDown", ("5,5", "3") -> "DoubleDown", ("5,5", "4") -> "DoubleDown", ("5,5", "5") -> "DoubleDown", ("5,5", "6") -> "DoubleDown", ("5,5", "7") -> "DoubleDown", ("5,5", "8") -> "DoubleDown", ("5,5", "9") -> "DoubleDown", ("5,5", "10") -> "Hit", ("5,5", "A") -> "Hit",
    ("6,6", "2") -> "Hit", ("6,6", "3") -> "Split", ("6,6", "4") -> "Split", ("6,6", "5") -> "Split", ("6,6", "6") -> "Split", ("6,6", "7") -> "Hit", ("6,6", "8") -> "Hit", ("6,6", "9") -> "Hit", ("6,6", "10") -> "Hit", ("6,6", "A") -> "Hit",
    ("7,7", "2") -> "Split", ("7,7", "3") -> "Split", ("7,7", "4") -> "Split", ("7,7", "5") -> "Split", ("7,7", "6") -> "Split", ("7,7", "7") -> "Split", ("7,7", "8") -> "Hit", ("7,7", "9") -> "Hit", ("7,7", "10") -> "Hit", ("7,7", "A") -> "Hit",
    ("8,8", "2") -> "Split", ("8,8", "3") -> "Split", ("8,8", "4") -> "Split", ("8,8", "5") -> "Split", ("8,8", "6") -> "Split", ("8,8", "7") -> "Split", ("8,8", "8") -> "Split", ("8,8", "9") -> "Split", ("8,8", "10") -> "Split", ("8,8", "A") -> "Split",
    ("9,9", "2") -> "Split", ("9,9", "3") -> "Split", ("9,9", "4") -> "Split", ("9,9", "5") -> "Split", ("9,9", "6") -> "Split", ("9,9", "7") -> "Stand", ("9,9", "8") -> "Split", ("9,9", "9") -> "Split", ("9,9", "10") -> "Stand", ("9,9", "A") -> "Stand",
    ("10,10", "2") -> "Stand", ("10,10", "3") -> "Stand", ("10,10", "4") -> "Stand", ("10,10", "5") -> "Stand", ("10,10", "6") -> "Stand", ("10,10", "7") -> "Stand", ("10,10", "8") -> "Stand", ("10,10", "9") -> "Stand", ("10,10", "10") -> "Stand", ("10,10", "A") -> "Stand",
    ("A,A", "2") -> "Split", ("A,A", "3") -> "Split", ("A,A", "4") -> "Split", ("A,A", "5") -> "Split", ("A,A", "6") -> "Split", ("A,A", "7") -> "Split", ("A,A", "8") -> "Split", ("A,A", "9") -> "Split", ("A,A", "10") -> "Split", ("A,A", "A") -> "Split")

}

class BotPlayer(name: String, bankRoll: Double, var betAmount: Double) extends Player(name, bankRoll, betAmount) with Hand with Logs {

  /** This method receives messages */
  override def act {
    loop {
      react {
        // Receives message to tell player to place its bet
        case Go =>
          debug(this + " received Go placing bet = " + betAmt + " from bankroll = " + bankroll)
          bet

        // Receives the dealer's up-card which is player's cue to play
        case Up(card) =>
          debug(this + " received dealer's up card = " + card)

          play(card)

        // Receives a card from the dealer
        case card: Card =>
          hitMe(card)

        // Receives a card after requesting DoubleDown
        case DoubleDown =>
          betAmt = betAmt * 2
        //hitMe(card)

        // Receives broke message
        case Broke =>
          debug(this + " received BROKE")

        // Receives message about dealt card
        case Observe(card, player, shoeSize) =>
          debug(this + " observed: " + card)

          observe(card, player, shoeSize)

        // Receives the table number I've been assigned to
        case TableInfo(tid: Int, minBet: Double, maxBet: Double) =>
          debug(this + " received table assignment tid = " + tid)
          assign(tid, minBet, maxBet)

        case Win(gain) =>
          val won = betAmt * gain

          bankroll += won

          debug(this + " received WIN " + won + " new bankroll = " + bankroll)

        case Lose(gain) =>
          val lost = betAmt * gain

          bankroll += lost

          debug(this + " received LOOSE " + lost + " new bankroll = " + bankroll)

        case Push(gain) =>
          debug(this + " received PUSH bankroll = " + bankroll)

        // Receives an ACK
        case Ok =>
          debug(this + " received Ok")

        // Receives something completely from left field
        case dontKnow =>
          // Got something we REALLY didn't expect
          debug(this + " received unexpected: " + dontKnow)
      }
    }
  }

  /**
   * Processes hit request.
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */
  override def hitMe(card: Card) {
    // Hit my hand with this card
    this.hit(card)

    debug(this + " received card " + card + " hand sz = " + cards.size + " value = " + value)

    // If I've received more than two cards, the extras must be in
    // response to my requests
    if (cards.size > 2)
      play(this.upcard)
  }

  /** Places a bet with the house */
  override def bet {
    if (bankroll < betAmt)
      return

    House ! Bet(pid, betAmt)
  }

  /**
   * Processes the dealer's upcard.
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */
  override def play(upcard: Card) {
    this.upcard = upcard

    // Compute my play strategy
    val request = analyze(upcard)

    debug(this + " request = " + request)

    // Don't send a request if we break since
    // deal will have moved on
    if (!this.broke)
      sender ! request

  }

  /** Analyzes my best play using a condensed form of Basic Strategy. */
  override def analyze(upcard: Card): Request = {

    var query: (String, String) = null
    var basicStrategy = "Hit"
    var request: Request = null

    if (cards(0).ace == true && cards(1).ace == true) {
      query = ("A," + "A", upcard.value.toString())
      basicStrategy = BotPlayer.pairBasicStrategy(query)
    } else if (cards(0).value == cards(1).value) {
      query = (cards(0).value.toString() + "," + cards(1).value.toString(), upcard.value.toString())
      basicStrategy = BotPlayer.pairBasicStrategy(query)
    } else if (cards(0).ace == true) {
      query = ("A," + cards(1).value.toString(), upcard.value.toString())
      basicStrategy = BotPlayer.softBasicStrategy(query)
    } else if (cards(1).ace == true) {
      query = ("A," + cards(0).value.toString(), upcard.value.toString())
      basicStrategy = BotPlayer.softBasicStrategy(query)
    } else {
      if (value <= 8)
        basicStrategy = "Hit"
      else if (value < 18) {
        query = (value.toString(), upcard.value.toString())
        basicStrategy = BotPlayer.hardBasicStrategy(query)
      } else
        basicStrategy = "Stand"

    }

    debug(this + "basic Strategy is " + basicStrategy)

    // Creates the request according to the Basic Strategy received from the Hash Maps

    basicStrategy match {
      case "Hit" =>
        request = Hit(pid)
      case "Stand" =>
        request = Stay(pid)
      case "DoubleDown" =>
        if (cards.size == 2)
          request = DoubleDown(pid)
        else
          request = Hit(pid)
      case "Split" =>
        request = Split(pid)
      case dontKnow =>
        debug("got" + dontKnow)
    }
    request
  }
}